package cn.jnetty.utils.create;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author 猪哥亮
 * @email 1030681978@qq.com
 * @date 2020/4/3 12:59
 * 描述：Class类扫描器.
 */
public class JnettyResourceScanning {
    private static final Logger logger = LoggerFactory.getLogger(JnettyResourceScanning.class);
    /**
     * URL protocol for a file in the file system: "file"
     */
    private static final String URL_PROTOCOL_FILE = "file";
    /**
     * URL protocol for an entry from a jar file: "jar"
     */
    private static final String URL_PROTOCOL_JAR = "jar";
    private static final String BACKSLASH = "/";

    /**
     * 私有化构造函数,这个类只用一次.
     */
    private JnettyResourceScanning() {
    }

    /**
     * 扫描指定目标下所有资源文件.
     * <p>
     * 仅仅只是扫描文件，至于咋处理由回调方法去分析
     *
     * @param packages 扫描的包名
     * @param
     */
    public static void scanPackage(String[] packages, ResourceCallback callback) {
        for (String packagePath : packages) {
            scanPackage(packagePath,callback);
        }
    }

    /**
     * 扫描指定目标下所有资源文件.
     *
     * @param packagePath 扫描的包名
     */
    private static void scanPackage(String packagePath,ResourceCallback callback) {
        // 处理一下包名到目录
        packagePath = packagePath.replace('.', '/').replace('\\', '/');
        if (!packagePath.endsWith(BACKSLASH)) {
            packagePath += BACKSLASH;
        }

        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                switch (url.getProtocol()) {
                    // "file"
                    case URL_PROTOCOL_FILE:
                        doFindFileResources(packagePath, new File(url.getFile()), callback);
                        break;
                    // "jar"
                    case URL_PROTOCOL_JAR:
                        doFindJarResources(url, callback, packagePath);
                        break;
                    default:
                        break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("扫描过程中出异常啦",e);
        }
    }

    private static void doFindJarResources(URL url, ResourceCallback callback, String rootEntryPath) throws IOException {
        JarURLConnection jarCon = (JarURLConnection) url.openConnection();

        try (JarFile jarFile = jarCon.getJarFile()) {
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) {
                String entryPath = entries.nextElement().getName();
                if (entryPath.startsWith(rootEntryPath)) {
                    findJarFile(entryPath, callback);
                }
            }
        }
    }

    /**
     * 递归扫描目录文件.
     *
     * @param packagePath
     * @param file
     * @param callback
     */
    private static void doFindFileResources(String packagePath, File file, ResourceCallback callback) {
        String path = file.getAbsolutePath();
        // 这个目录不存在，忽略
        if (!file.exists()) {
            logger.info("Skipping [{}] because it does not exist", path);
            return;
        }

        // 这个目录不可以读，忽略
        if (!file.canRead()) {
            logger.info("Cannot search for matching files underneath directory [{}] because the application is not allowed to read the directory", path);
            return;
        }

        // 如果这是一个目录，继续向下找
        else if (file.isDirectory()) {
            findDir(packagePath, file, callback);
        }

        // 如果是一个文件，交作业
        else if (file.isFile()) {
            findFile(packagePath, file, callback);
        }
    }

    /**
     * 查找到一个目录.
     *
     * @param packagePath
     * @param dir         文件
     * @param callback    发现文件的回调接口
     */
    private static void findDir(String packagePath, File dir, ResourceCallback callback) {

        File[] dirContents = dir.listFiles();

        // 目录下没有任何东东，忽略
        if (dirContents == null) {
            logger.warn("Could not retrieve contents of directory [{}]", dir.getAbsolutePath());
            return;
        }

        for (File content : dirContents) {

            // 文件
            if (content.isFile()) {
                findFile(packagePath, content, callback);
            }

            // 是目录则继续
            else if (content.isDirectory()) {
                // 处理目录下的文件，需要把目录修正
                findDir(packagePath + content.getName() + "/", content, callback);
            }
        }
    }

    /**
     * 查找到一个Jar文件.
     *
     * @param entryPath Jar的资源路径
     * @param callback  发现文件的回调接口
     */
    private static void findJarFile(String entryPath, ResourceCallback callback) {
        callback.handle(new JarResource(entryPath));
    }

    /**
     * 查找到一个文件.
     *
     * @param packagePath 文件所在目录
     * @param file        文件
     * @param callback    发现文件的回调接口
     */
    private static void findFile(String packagePath, File file, ResourceCallback callback) {
        callback.handle(new FileResource(packagePath, file));
    }

    private static Class<?> analysisResource(Resource resource) {
        String resourceName = resource.getName();

        // 忽略 package-info.class
        if (PACKAGE_INFO_CLASS.equals(resourceName)) {
            return null;
        }

        // 忽略非Class文件
        if (!resourceName.endsWith(CLASS_SUFFIX)) {
            return null;
        }
        // Class快速载入
        return ClassUtils.loadClass(resourceName.substring(0, resourceName.length() - 6).replaceAll("[/\\\\]", "."));
    }

    private static final String PACKAGE_INFO_CLASS = "package-info.class";
    private static final String CLASS_SUFFIX = ".class";

    public static void main(String[] args) {
        iocLoader((resource) -> analysisResource(resource),"");
    }

    public static void iocLoader(ResourceCallback callback,String... packages) {
//        scanPackage(packages, (resource) -> analysisResource(resource));
        scanPackage(packages,callback);
    }
}
